package io.geekidea.boot.framework.aop;

import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUtil;
import com.hwtx.form.annotation.Log;
import com.hwtx.form.domain.def.FormDef;
import com.hwtx.form.domain.ds.DataSourceContextHolder;
import com.hwtx.form.domain.handle.FormI18nHandle;
import com.hwtx.form.domain.repo.SysLogRepo;
import com.hwtx.form.domain.service.FormService;
import com.hwtx.form.util.BeanUtil;
import io.geekidea.boot.auth.util.CommonLoginUtil;
import io.geekidea.boot.auth.util.TokenUtil;
import io.geekidea.boot.common.constant.CommonConstant;
import io.geekidea.boot.common.enums.SystemType;
import io.geekidea.boot.config.properties.LogAopProperties;
import io.geekidea.boot.framework.bean.IpRegion;
import io.geekidea.boot.framework.exception.BusinessException;
import io.geekidea.boot.system.entity.SysLog;
import io.geekidea.boot.util.IpRegionUtil;
import io.geekidea.boot.util.IpUtil;
import io.geekidea.boot.util.SystemTypeUtil;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.MDC;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.core.annotation.Order;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import java.lang.reflect.Method;
import java.util.Date;
import java.util.Objects;

import static com.hwtx.form.util.JacksonUtil.object2json;

@Slf4j
@Aspect
@Component
@Order(1)
@ConditionalOnProperty(name = "boot.form.enable-log", havingValue = "true", matchIfMissing = true)
public class FormAround {

    private static final String REFERER = "Referer";
    private static final String USER_AGENT = "User-Agent";
    private static final String ORIGIN = "Origin";
    @Resource
    LogAopProperties logAopProperties;
    @Resource
    private FormService formService;
    @Resource
    private SysLogRepo sysLogRepo;

    private Log getLogAnnotation(ProceedingJoinPoint joinPoint) {
        Method method = getMethod(joinPoint);
        return method.getAnnotation(Log.class);
    }

    private Method getMethod(ProceedingJoinPoint joinPoint) {
        Signature signature = joinPoint.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        return methodSignature.getMethod();
    }

    @Around("@annotation(com.hwtx.form.annotation.PerfEnable)")
    public Object trackExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
        long start = System.currentTimeMillis();
        Object proceed = joinPoint.proceed();
        long executionTime = System.currentTimeMillis() - start;
        log.info("{} executed in {} ms", joinPoint.getSignature(), executionTime);
        return proceed;
    }

    @Around("@annotation(com.hwtx.form.annotation.Action)")
    public Object actionAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
        if (!logAopProperties.isEnable()) {
            return joinPoint.proceed();
        }
        long startTime = System.currentTimeMillis();
        Log logAnnotation = getLogAnnotation(joinPoint);
        if (logAnnotation == null || StringUtils.isEmpty(logAnnotation.value())) {
            return joinPoint.proceed();
        }
        SysLog sysLog = new SysLog();
        sysLog.setLogName(logAnnotation.value());
        Object result;
        try {
            handleBefore(sysLog);
            sysLog.setRequestMethod(getMethod(joinPoint).getName());
            sysLog.setRequestParam(object2json(joinPoint.getArgs()));
            result = joinPoint.proceed();
        } catch (Throwable e) {
            // 执行方法异常处理
            handleException(e, sysLog);
            throw e;
        } finally {
            // 执行方法结束后
            handleFinally(sysLog);
        }
        long endTime = System.currentTimeMillis();
        log.info("处理action请求-{}耗时：{}ms", sysLog.getRequestMethod(), endTime - startTime);
        return result;
    }

    @Around("execution(* com.hwtx.form.controller..*Controller.*(..))")
    public Object controllerAroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
        if (!logAopProperties.isEnable()) {
            return joinPoint.proceed();
        }
        long startTime = System.currentTimeMillis();
        Log logAnnotation = getLogAnnotation(joinPoint);
        if (logAnnotation == null || StringUtils.isEmpty(logAnnotation.key())) {
            return joinPoint.proceed();
        }
        FormDef formDef = formService.getFormDef(getFormId(joinPoint, logAnnotation.key()));
        if (formDef == null) {
            return joinPoint.proceed();
        }
        if (formDef.getMeta() == null) {
            throw new BusinessException("表单元数据不存在，formId = " + formDef.getFormId());
        }
        String title = formDef.getMeta().getTitle();
        SysLog sysLog = new SysLog();
        sysLog.setFormId(formDef.getFormId());
        sysLog.setLogName(FormI18nHandle.getValue(title));
        Object result;
        try {
            handleBefore(sysLog);
            result = joinPoint.proceed();
        } catch (Throwable e) {
            // 执行方法异常处理
            handleException(e, sysLog);
            throw e;
        } finally {
            // 执行方法结束后
            handleFinally(sysLog);
        }
        long endTime = System.currentTimeMillis();
        log.info("处理请求-{}耗时：{}ms", sysLog.getRequestUrl(), endTime - startTime);
        return result;
    }

    private Long getFormId(ProceedingJoinPoint joinPoint, String logKey) {
        Signature signature = joinPoint.getSignature();
        if (!(signature instanceof MethodSignature methodSignature)) {
            throw new IllegalArgumentException("该注解只能用于方法");
        }
        String[] parameterNames = methodSignature.getParameterNames();
        Object[] args = joinPoint.getArgs();
        Object paramValue = null;
        String[] keys = logKey.split("\\.");
        String paramName = keys[0];
        for (int i = 0; i < parameterNames.length; i++) {
            if (paramName.equals(parameterNames[i])) {
                paramValue = args[i];
                break;
            }
        }
        if (paramValue != null && keys.length > 1) {
            for (int i = 1; i < keys.length; i++) {
                paramValue = BeanUtil.getFieldValue(paramValue, keys[i]);
                if (paramValue == null) {
                    break;
                }
            }
        }
        if (paramValue == null) {
            throw new IllegalArgumentException("无法获取formId,@Log key =  " + logKey);
        }
        if (logKey.contains("page")) {
            return formService.getFormIdByName(paramValue.toString());
        }
        return Long.valueOf(paramValue.toString());
    }

    private void handleFinally(SysLog sysLog) {
        try {
            String requestTime = sysLog.getRequestTime();
            Date requestDate = DateUtil.parse(requestTime, DatePattern.NORM_DATETIME_MS_PATTERN);
            long startTime = requestDate.getTime();
            Date responseDate = new Date();
            long nowTime = responseDate.getTime();
            long diffTime = nowTime - startTime;
            String responseTime = DateUtil.format(responseDate, DatePattern.NORM_DATETIME_MS_PATTERN);
            sysLog.setResponseTime(responseTime);
            sysLog.setDiffTime(diffTime);
            saveSysLog(sysLog);
        } catch (Exception e) {
            log.error("执行方法结束后处理异常", e);
        }
    }

    @Async
    public void saveSysLog(SysLog sysLog) {
        String key = DataSourceContextHolder.getDataSourceKey();
        try {
            if (sysLog != null) {
                DataSourceContextHolder.setDefaultDsKey();
                if (!sysLogRepo.save(sysLog)) {
                    log.error("保存操作日志错误 {}", sysLog);
                }
            }
        } catch (Exception e) {
            log.error("保存操作日志异常", e);
        } finally {
            DataSourceContextHolder.setDataSourceKey(key);
        }
    }

    private void handleException(Throwable e, SysLog sysLog) {
        try {
            String exceptionClassName = e.getClass().getSimpleName();
            sysLog.setExceptionName(exceptionClassName);
            String exceptionMessage = e.getMessage();
            sysLog.setExceptionMessage(exceptionMessage);
        } catch (Exception ex) {
            log.error("", ex);
        }
    }

    private void handleBefore(SysLog sysLog) {
        try {
            HttpServletRequest request = ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();
            sysLog.setRequestUrl(request.getServletPath());
            sysLog.setRequestTime(DateUtil.format(new Date(), DatePattern.NORM_DATETIME_MS_PATTERN));
            String traceId = MDC.get(CommonConstant.TRACE_ID);
            sysLog.setTraceId(traceId);
            handleIpArea(sysLog);
            sysLog.setContentType(request.getContentType());
            sysLog.setRequestMethod(request.getMethod());
            sysLog.setRequestParam(object2json(request.getParameterMap()));
            sysLog.setReferer(request.getHeader(REFERER));
            sysLog.setUserAgent(request.getHeader(USER_AGENT));
            sysLog.setOrigin(request.getHeader(ORIGIN));
            String token = TokenUtil.getToken();
            if (StringUtils.isNotBlank(token)) {
                SystemType systemType = SystemTypeUtil.getSystemTypeByToken(token);
                if (systemType != null) {
                    // 系统类型 1：Admin管理后台，2：APP移动端端
                    sysLog.setSource(systemType.getCode());
                    // 记录用户ID
                    try {
                        sysLog.setUserName(CommonLoginUtil.getUserName(systemType));
                    } catch (Exception e) {
                        log.warn("获取用户ID异常", e);
                    }
                }
            }
        } catch (Exception e) {
            log.error("", e);
        }
    }

    private void handleIpArea(SysLog sysLog) {
        // 设置IP
        String ip = IpUtil.getRequestIp();
        sysLog.setRequestIp(ip);
        try {
            // 设置IP归属地信息
            IpRegion ipRegion = IpRegionUtil.getIpRegion(ip);
            if (ipRegion != null) {
                sysLog.setIpCountry(ipRegion.getCountry());
                sysLog.setIpProvince(ipRegion.getProvince());
                sysLog.setIpCity(ipRegion.getCity());
                sysLog.setIpAreaDesc(ipRegion.getAreaDesc());
                sysLog.setIpIsp(ipRegion.getIsp());
            }
        } catch (Exception e) {
            log.error("获取IP区域异常", e);
        }
    }
}
